﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VsxFactory.Modeling.VisualStudio;
using Microsoft.VisualStudio.Modeling;
using System.Diagnostics.Contracts;
using Microsoft.VisualStudio.Modeling.Validation;
using EnvDTE;
using VsxFactory.Modeling.Strategies.Rules;

namespace VsxFactory.Modeling.Strategies
{
    /// <summary>
    /// Classe de base pour les stratégies répondant aux événements d'un Store.
    /// </summary>
    public abstract class ModelingStrategy : IGenerationStrategy, ISupportsGenerateCommand
    {
        public IStrategiesHost Host { get; private set; }
        public DSLModelObserver ModelObserver { get; private set; }
        private IVsProject defaultProject;
        protected IVsProjectFileItem ModelNode { get; private set; }
        protected Store Store {private set;get;}
        public Func<IStrategyContext, ModelElement, GenerationSpec> CreateGenerationSpec { get; set; }
        private bool _isInitialized;

        protected ModelingStrategy()
        {
           
        }

        public virtual bool Initialize(IStrategiesHost host, IServiceProvider serviceProvider, IVsProjectFileItem node)
        {
            Contract.Requires(host != null);
            Contract.Requires(node != null);

            if (_isInitialized)
                return true;

            _isInitialized = true;

            this.Host = host;
            defaultProject = node.Project;
            Initialize();
            if (serviceProvider is Store)
            {
                this.Store = serviceProvider as Store;
                this.ModelNode = node;
                ModelObserver = new DSLModelObserver(Store);
                ModelObserver.ElementAdded += new EventHandler<DSLModelElementEventArgs>(OnModelElementAdded);
                ModelObserver.ElementRemoved += new EventHandler<DSLModelElementEventArgs>(OnModelElementRemoved);
                ModelObserver.PropertyChanged += new EventHandler<DSLModelPropertyEventArgs>(OnModelElementPropertyChanged);
            }
            return true;
        }

        protected virtual void Initialize()
        {
            if(CreateGenerationSpec == null)
                CreateGenerationSpec = (ctx, mel) => new GenerationSpec(mel, defaultProject, Guid.Empty);
        }

        void OnModelElementPropertyChanged(object sender, DSLModelPropertyEventArgs e)
        {
            ValidationController vc = new ValidationController();
            if(vc.Validate(e.ModelElement, ValidationCategories.Save))
            {
                var ctx = Host.GetStrategyContext(this, ModelNode, CodeGenerationEvent.OnModelElementPropertyChanged);
                ctx.Element = e.ModelElement;
                ctx.PropertyId = e.PropertyId;
                ctx.OldPropertyValue = e.OldValue;
                try
                {
                    if(OnPreGeneration(ctx))
                    {
                        Generate(ctx);
                        OnPostGeneration(ctx);
                    }
                }
                catch(Exception ex)
                {
                    ctx.WriteLogMessage("Error on model element property changed : {0}", ex.Message);
                }
            }
        }

        void OnModelElementRemoved(object sender, DSLModelElementEventArgs e)
        {
            var ctx = Host.GetStrategyContext(this, ModelNode, CodeGenerationEvent.OnModelElementRemoved);
            ctx.Element = e.ModelElement;
            try
            {
                if(OnPreGeneration(ctx))
                {
                    if( Generate(ctx) )
                        OnPostGeneration(ctx);
                }
            }
            catch(Exception ex)
            {
                ctx.WriteLogMessage("Error on model element removed : {0}", ex.Message);
            }
        }

        void OnModelElementAdded(object sender, DSLModelElementEventArgs e)
        {
            ValidationController vc = new ValidationController();
            if (vc.Validate(e.ModelElement, ValidationCategories.Save))
            {
                var ctx = Host.GetStrategyContext(this, ModelNode, CodeGenerationEvent.OnModelElementAdded);
                ctx.Element = e.ModelElement;
                try
                {
                    if(OnPreGeneration(ctx))
                    {
                        if( Generate(ctx, true))
                            OnPostGeneration(ctx);
                    }
                }
                catch(Exception ex)
                {
                    ctx.WriteLogMessage("Error on model element added : {0}", ex.Message);
                }
            }
        }

        protected virtual bool CheckModelIsValid()
        {
            if (ModelObserver == null)
                return true;
            ValidationController vc = new ValidationController();
            return vc.Validate(ModelObserver.Store, ValidationCategories.Save);
        }

        public virtual void GenerateArtifacts(IVsProjectFileItem document)
        {
            System.Diagnostics.Contracts.Contract.Requires(document != null);
            if (CheckModelIsValid())
            {
                var ctx = Host.GetStrategyContext(this, document, CodeGenerationEvent.OnModelElementAdded | CodeGenerationEvent.OnModelFileSaved);
                if (OnPreGeneration(ctx))
                {
                    // Recherche des éléments supportant la génération
                    bool flag = false;
                    foreach (var element in Store.ElementDirectory.FindElements<ModelElement>(true))
                    {
                        ctx.Element = element;
                        flag |= Generate(ctx);
                    }
                    if(flag)
                        OnPostGeneration(ctx);
                }
            }
        }

        public void OnDocumentSaved(IVsProjectFileItem document)
        {
            Contract.Requires(document != null);
            if (CheckModelIsValid())
            {
                var ctx = Host.GetStrategyContext(this, document, CodeGenerationEvent.OnModelFileSaved);
                if (OnPreGeneration(ctx))
                {
                    if( Generate(ctx) )
                        OnPostGeneration(ctx);
                }
            }
        }

        protected virtual bool OnPreGeneration(IStrategyContext ctx)
        {
            return true;
        }

        protected virtual void OnPostGeneration(IStrategyContext ctx)
        {
        }

        protected abstract bool Generate(IStrategyContext ctx, bool force=false);

        public bool OnDocumentClosed(IVsProjectFileItem document)
        {
            System.Diagnostics.Contracts.Contract.Requires(document != null);
            if (ModelObserver != null)
            {
                ModelObserver.ElementAdded -= new EventHandler<DSLModelElementEventArgs>(OnModelElementAdded);
                ModelObserver.ElementRemoved -= new EventHandler<DSLModelElementEventArgs>(OnModelElementRemoved);
                ModelObserver.PropertyChanged -= new EventHandler<DSLModelPropertyEventArgs>(OnModelElementPropertyChanged);
            }
            return true;
        }

        public virtual bool CanExecuteGenerateCommand(IVsProjectItem item)
        {
            return false;
        }
    }
}
